﻿#include <signal.h>

#include <stdio.h>
#include <unistd.h>

#include <string.h>
#include <errno.h>
#include <sys/wait.h> //waitpid

#include "ngx_macro.h"
#include "ngx_func.h"
#include "ngx_c_conf.h"
/*
    信号相关结构体
*/
extern int ngx_process;
extern sig_atomic_t ngx_reap;
typedef struct
{
    int signo;           // 信号对应数字
    const char *signame; // 对应名字
    // 信号预处理函数
    void (*handle)(int signo, siginfo_t *siginfo, void *ucontext); // 函数指针,   siginfo_t:系统定义的结构
} ngx_signal_t;

// 声明一个信号处理函数
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext);
static void ngx_process_get_status(void);   

ngx_signal_t signals[] = {
    // signo      signame             handler
    {SIGHUP, "SIGHUP", ngx_signal_handler},   // 终端断开信号，对于守护进程常用于reload重载配置文件通知--标识1
    {SIGINT, "SIGINT", ngx_signal_handler},   // 标识2
    {SIGTERM, "SIGTERM", ngx_signal_handler}, // 标识15
    {SIGCHLD, "SIGCHLD", ngx_signal_handler}, // 子进程退出时，父进程会收到这个信号--标识17
    {SIGQUIT, "SIGQUIT", ngx_signal_handler}, // 标识3
    {SIGIO, "SIGIO", ngx_signal_handler},     // 指示一个异步I/O事件【通用异步I/O信号】
    {SIGSYS, "SIGSYS, SIG_IGN", NULL},        // 我们想忽略这个信号，SIGSYS表示收到了一个无效系统调用，如果我们不忽略，进程会被操作系统杀死，--标识31
                                              // 所以我们把handler设置为NULL，代表 我要求忽略这个信号，请求操作系统不要执行缺省的该信号处理动作（杀掉我）
    //...日后根据需要再继续增加
    {0, NULL, NULL} // 信号对应的数字至少是1，所以可以用0作为一个特殊标记
};

// 初始化信号的函数，用于注册信号处理程序
// 返回值：0成功  ，-1失败
int ngx_init_signals()
{
    ngx_signal_t *sig;
    struct sigaction sa;

    for (sig = signals; sig->signo != 0; sig++)
    {
        memset(&sa, 0, sizeof(struct sigaction));

        // 判断是否自定义处理函数
        if (sig->handle != nullptr)
        {
            sa.sa_sigaction = sig->handle;
            // 想让sa.sa_sigaction指定的信号处理程序(函数)生效，你就把sa_flags设定为SA_SIGINFO
            sa.sa_flags = SA_SIGINFO;
        }
        else
        {
            sa.sa_handler = SIG_IGN;
        }
        // 这里.sa_mask是个信号集（描述信号的集合），用于表示要阻塞的信号，sigemptyset()：把信号集中的所有信号清0，本意就是不准备阻塞任何信号；
        sigemptyset(&sa.sa_mask);

        // 设置信号处理动作(信号处理函数)，说白了这里就是让这个信号来了后调用我的处理程序，有个老的同类函数叫signal，不过signal这个函数被认为是不可靠信号语义，不建议使用，大家统一用sigaction
        if (sigaction(sig->signo, &sa, NULL) == -1) // 参数1：要操作的信号
                                                    // 参数2：主要就是那个信号处理函数以及执行信号处理函数时候要屏蔽的信号等等内容
                                                    // 参数3：返回以往的对信号的处理方式【跟sigprocmask()函数边的第三个参数是的】，跟参数2同一个类型，我们这里不需要这个东西，所以直接设置为NULL；
        {
            ngx_log_error_core(NGX_LOG_EMERG, errno, "sigaction(%s) failed", sig->signame); // 显示到日志文件中去的
            return -1;                                                                      // 有失败就直接返回
        }
        else
        {
            // ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) succed!",sig->signame);     //成功不用写日志
            ngx_log_stderr(0, "sigaction(%s) succed!", sig->signame); // 直接往屏幕上打印看看 ，不需要时可以去掉
        }
    } // end for
    return 0; // 成功
}

// 信号处理函数
// siginfo：这个系统定义的结构中包含了信号产生原因的有关信息
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{
    ngx_signal_t *sig; // 自定义结构
    char *action;      // 一个字符串，用于记录一个动作字符串以往日志文件中写
    for (sig = signals; sig != 0; sig++)
    {
        if (sig->signo == signo) // 找到对应信号，即可处理
            break;
    }
    action = (char *)""; // 目前还没有什么动作；

    if (ngx_process == NGX_PROCESS_MASTER) // master进程，管理进程，处理的信号一般会比较多
    {
        // master进程的往这里走
        switch (signo)
        {
        case SIGCHLD:     // 一般子进程退出会收到该信号
            ngx_reap = 1; // 标记子进程状态变化，日后master主进程的for(;;)循环中可能会用到这个变量【比如重新产生一个子进程】
            break;

            //.....其他信号处理以后待增加

        default:
            break;
        } // end switch
    }
    else if (ngx_process == NGX_PROCESS_WORKER) // worker进程，具体干活的进程，处理的信号相对比较少
    {
        // TODO
    }
    else
    {
        // 非master非worker进程TODO
    }

    // 这里记录一些日志信息
    // siginfo这个
    if (siginfo && siginfo->si_pid) // si_pid = sending process ID【发送该信号的进程id】
    {
        ngx_log_error_core(NGX_LOG_NOTICE, 0, "signal %d (%s) received from %P%s", signo, sig->signame, siginfo->si_pid, action);
    }
    else
    {
        // 没有发送该信号的进程id，所以不显示发送该信号的进程id
        ngx_log_error_core(NGX_LOG_NOTICE, 0, "signal %d (%s) received %s", signo, sig->signame, action);
    }

    //.......其他需要扩展的将来再处理；

    // 子进程状态有变化，通常是意外退出【既然官方是在这里处理，我们也学习官方在这里处理】
    if (signo == SIGCHLD)
    {
        ngx_process_get_status(); // 获取子进程的结束状态
    } // end if

    return;
}

void ngx_process_get_status()
{
    pid_t pid;
    int status;
    int err;
    int one = 0; // 抄自官方nginx，应该是标记信号正常处理过一次
    for (;;)
    {

        // 第一次waitpid返回一个> 0值，表示成功，后边显示 2019/01/14 21:43:38 [alert] 3375: pid = 3377 exited on signal 9【SIGKILL】
        // 第二次再循环回来，再次调用waitpid会返回一个0(只有waitpid的第三个参数为WNOHANG时waitpid才会返回0)，然后这里有return来退出；
        pid = waitpid(-1, &status, WNOHANG); // 第一个参数为-1，表示等待任何子进程，
                                             // 第二个参数：保存子进程的状态信息。
                                             // 第三个参数：提供额外选项，WNOHANG表示不要阻塞，让这个waitpid()立即返回

        if (pid == 0)
        {
            return;
        } // end if(pid == 0)
        //-------------------------------
        if (pid == -1) // 这表示这个waitpid调用有错误，有错误也理解返回出去，我们管不了这么多
        {
            // 这里处理代码抄自官方nginx，主要目的是打印一些日志。考虑到这些代码也许比较成熟，所以，就基本保持原样照抄吧；
            err = errno;
            if (err == EINTR) // 调用被某个信号中断
            {
                continue;
            }

            if (err == ECHILD && one) // 没有子进程
            {
                return;
            }

            if (err == ECHILD) // 没有子进程
            {
                ngx_log_error_core(NGX_LOG_INFO, err, "waitpid() failed!");
                return;
            }
            ngx_log_error_core(NGX_LOG_ALERT, err, "waitpid() failed!");
            return;
        } // end if(pid == -1)

        // 走到这里，表示  成功【返回进程id】 ，这里根据官方写法，打印一些日志来记录子进程的退出
        one = 1;              // 标记waitpid()返回了正常的返回值
        if (WTERMSIG(status)) // 获取使子进程终止的信号编号
        {
            ngx_log_error_core(NGX_LOG_ALERT, 0, "pid = %P exited on signal %d!", pid, WTERMSIG(status)); // 获取使子进程终止的信号编号
        }
        else
        {
            ngx_log_error_core(NGX_LOG_NOTICE, 0, "pid = %P exited with code %d!", pid, WEXITSTATUS(status)); // WEXITSTATUS()获取子进程传递给exit或者_exit参数的低八位
        }
    }
    return;
}
